package com.avcompris.util;

import static com.avcompris.util.ExceptionUtils.nonNullArgument;

import java.io.Serializable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import com.avcompris.common.annotation.Nullable;

/**
 * wrapper for Java objects that represent a YAML structure.
 */
class YamledImpl implements Yamled, Serializable {

	/**
	 * for serialization.
	 */
	private static final long serialVersionUID = 859718759086952563L;

	/**
	 * @param yaml
	 *            the delegate YAML structure.
	 */
	private YamledImpl(final Object yaml) {

		this.yaml = nonNullArgument(yaml, "yaml");
	}

	/**
	 * the delegate YAML structure.
	 */
	private final Object yaml;

	@Override
	public Yamled get(final String propertyName) {

		Object y = yaml;

		while (true) {

			final Object value = YamlUtils.getObjectProperty(y, propertyName);

			if (value != null) {
				return wrapToYamled(value);
			}

			if (!Map.class.isInstance(y) || ((Map<?, ?>) y).size() != 1) {
				break;
			}

			y = ((Map<?, ?>) y).values().iterator().next();
		}

		throw new IllegalArgumentException("Cannot find property: "
				+ propertyName + " in yaml: " + yaml);
	}

	@Override
	public String label() {

		return (String) ((Map<?, ?>) yaml).keySet().iterator().next();
	}

	@Override
	public Yamled[] items(final String propertyName) {

		Object y = yaml;

		while (true) {

			final Collection<?> list = YamlUtils.getListProperty(y,
					propertyName);

			if (list != null) {
				return getList(list);
			}

			if (!Map.class.isInstance(y) || ((Map<?, ?>) y).size() != 1) {
				break;
			}

			y = ((Map<?, ?>) y).values().iterator().next();
		}

		throw new IllegalArgumentException("Cannot find property: "
				+ propertyName + " in yaml: " + yaml);
	}

	private Yamled[] getList(final Collection<?> list) {

		final Yamled[] yamledArray = new Yamled[list.size()];

		int i = 0;

		for (final Object object : list) {

			yamledArray[i] = wrapToYamled(object);

			++i;
		}

		return yamledArray;
	}

	@Override
	public Yamled[] items() {

		Object y = yaml;

		while (true) {

			if (List.class.isInstance(y)) {
				return getList((List<?>) y);
			}

			if (!Map.class.isInstance(y) || ((Map<?, ?>) y).size() != 1) {
				break;
			}

			y = ((Map<?, ?>) y).values().iterator().next();
		}

		throw new IllegalArgumentException("Cannot find list in yaml: " + yaml);
	}

	@Override
	public String string(int index) {

		return strings()[index];
	}

	@Override
	public String[] strings() {

		final Yamled[] values = items();

		final String[] array = new String[values.length];

		for (int i = 0; i < array.length; ++i) {

			array[i] = values[i].asString();
		}

		return array;
	}

	@Override
	public int hashCode() {

		return yaml.hashCode();
	}

	@Override
	public boolean equals(final Object o) {

		if (o == null) {
			return false;
		}

		if (getClass().equals(o.getClass())) {
			return yaml.equals(((YamledImpl) o).yaml);
		}

		return false;
	}

	@Override
	public String toString() {

		return yaml.toString();
	}

	@Override
	public String asString() {

		if (String.class.isInstance(yaml)) {

			return (String) yaml;
		}

		throw new IllegalStateException(
				"Internal yaml object is not a String: "
						+ yaml.getClass().getName());
	}

	@Override
	public boolean isString() {

		return String.class.isInstance(yaml);
	}

	@Override
	public Yamled item(final int index) {

		return items()[index];
	}

	/**
	 * wrap a YAML object to a {@link Yamled} object.
	 */
	@Nullable
	static Yamled wrapToYamled(@Nullable final Object yaml) {

		if (yaml == null) {
			return null;
		}

		// nonNullArgument(yaml, "yaml");

		final Yamled yamled = new YamledImpl(yaml);

		if (Map.class.isInstance(yaml)) {

			return (Yamled) Proxy.newProxyInstance(Thread.currentThread()
					.getContextClassLoader(), new Class[] { //
					Yamled.class, Map.class }, new InvocationHandler() {
						@Override
						public Object invoke(final Object proxy,
								final Method method, final Object[] args)
								throws Throwable {

							if (Map.class.equals(method.getDeclaringClass())) {

								return method.invoke(yaml, args);
							}

							return method.invoke(yamled, args);
						}
					});
		}

		if (List.class.isInstance(yaml)) {

			return (Yamled) Proxy.newProxyInstance(Thread.currentThread()
					.getContextClassLoader(), new Class[] { //
					Yamled.class, List.class }, new InvocationHandler() {
						@Override
						public Object invoke(final Object proxy,
								final Method method, final Object[] args)
								throws Throwable {

							if (List.class.equals(method.getDeclaringClass())) {

								return method.invoke(yaml, args);
							}

							return method.invoke(yamled, args);
						}
					});
		}

		return yamled;
	}
}
